-
Notifications
You must be signed in to change notification settings - Fork 4.2k
Extensible Fixed Statement #24469
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Extensible Fixed Statement #24469
Conversation
@dotnet/roslyn-compiler - please review. This is a 7.3 feature PR to a feature branch. |
REM Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. | ||
|
||
REM Run the node generator to create new bound tree node definitions. | ||
REM You must run this in the directory containing Generated.cs. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we should remove this file.
I've been using build\scripts\generate-compiler-code.cmd
. That one updates all the generated files at once. #Closed
return true; | ||
} | ||
|
||
private MethodSymbol GetDangerousGetPinnableReferenceMethod(BoundExpression initializer, DiagnosticBag additionalDiagnostics) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: no need for "Dangerous" here #WontFix
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we are not keeping "Dangerous" in the name. Once I implement the name change this will be renamed too
In reply to: 167647561 [](ancestors = 167647561)
elementType = ((BoundAddressOfOperator)initializerOpt).Operand.Type; | ||
getPinnableMethod = GetDangerousGetPinnableReferenceMethod(initializerOpt, additionalDiagnostics); | ||
|
||
bool extensisbleFixedEnabled = ((CSharpParseOptions)initializerOpt.SyntaxTree.Options)?.IsFeatureEnabled(MessageID.IDS_FeatureExtensibleFixedStatement) != false; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
typo in "extensible" #Closed
// check for String | ||
// NOTE: We will allow DangerousGetPinnableReferenceMethod to take precendence, but only if it is a member of System.String | ||
if (initializerType.SpecialType == SpecialType.System_String && | ||
((object)getPinnableMethod == null || !extensisbleFixedEnabled || getPinnableMethod.ContainingType.SpecialType != SpecialType.System_String)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
extensisbleFixedEnabled [](start = 67, length = 23)
I don't think the presence/absence of the feature flag should affect binding. Only diagnostics should be conditional on the feature flag.
We could probably remove extensisbleFixedEnabled
entirely and rely on CheckFeatureAvailability
below. #Closed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a very special case. We want that extensible pinnable to win over when implemented on System.String.
In reply to: 167648804 [](ancestors = 167648804)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You a right though - we should not check extensisbleFixedEnabled here.
The implementors of System.String will guarantee backwards compatibility of the new pattern. So this is more a matter of runtime versioning, not compiler versioning.
In reply to: 169196056 [](ancestors = 169196056,167648804)
if (extensisbleFixedEnabled) | ||
{ | ||
// not a specially known type, check for getPinnableMethod | ||
if (getPinnableMethod != null) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
getPinnableMethod [](start = 32, length = 17)
cast to object? #Closed
break; | ||
} | ||
else | ||
else if (additionalDiagnostics != null) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seems like additionalDiagnostics
is always set. #Closed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
not always. it is reported only on cases when we found the member, but something was wrong with it.
The idea is to help implementers of the method, but not complain about the method when it is clearly not provided.
In reply to: 167649336 [](ancestors = 167649336)
// convert to generate any additional conversion errors | ||
initializerOpt = GenerateConversionForAssignment(declType, initializerOpt, diagnostics); | ||
return true; | ||
// did not find anythig callable |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
typo: anything #Closed
); | ||
} | ||
|
||
#endregion Custom fixed statement tests |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
#endregion Custom fixed statement tests [](start = 7, length = 40)
Can we test the following?
fixed (int* p = (dynamic)new Fixable())
GetPinnable
that returnsref readonly
- check semantic model (although it looks like the selected
GetPinnable
method will not be exposed) - test with ambiguous
GetPinnable
method (maybe one instance method and one extension, or maybe two extensions) #Closed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed is currently not exposed in semantic model at all, so there is no way to test the differences.
For other suggestions more tests were added.
In reply to: 167650001 [](ancestors = 167650001)
<value>The given expression cannot be used in a fixed statement</value> | ||
</data> | ||
<data name="ERR_FixableHelperDoesNotFitThePattern" xml:space="preserve"> | ||
<value>The method '{0}' does not meet requirements necessary </value> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The method '{0}' does not meet requirements necessary [](start = 11, length = 53)
I feel both messages should give more information about what is expected. #Closed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
else | ||
|
||
var analyzedArguments = AnalyzedArguments.GetInstance(); | ||
BoundExpression getPinnableReferenceCall = BindMethodGroupInvocation(initializer.Syntax, initializer.Syntax, methodName, (BoundMethodGroup)boundAccess, analyzedArguments, bindingDiagnostics, queryClause: null, allowUnexpandedForm: false); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: please break those extra-long lines #Closed
DiagnosticBag bindingDiagnostics = DiagnosticBag.GetInstance(); | ||
try | ||
{ | ||
var boundAccess = BindInstanceMemberAccess(initializer.Syntax, initializer.Syntax, initializer, methodName, rightArity:0, typeArgumentsSyntax: default, typeArguments: default, invoked:true, bindingDiagnostics); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
invoked:true [](start = 192, length = 12)
Nit: formatting on invoked:true
and some other arguments #Closed
|
||
return true; | ||
var getPinnableReferenceMethod = call.Method; | ||
if (getPinnableReferenceMethod is ErrorMethodSymbol || |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we avoid testing a specific type? #Closed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is how we test for error method symbol in other places.
In reply to: 167652407 [](ancestors = 167652407)
// (6,25): error CS8320: Feature 'extensible fixed statement' is not available in C# 7.2. Please use language version 7.3 or greater. | ||
// fixed (int* p = new Fixable()) | ||
Diagnostic(ErrorCode.ERR_FeatureNotAvailableInVersion7_2, "new Fixable()").WithArguments("extensible fixed statement", "7.3").WithLocation(6, 25), | ||
// (6,25): error CS9365: The given expression cannot be used in a fixed statement |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
CS9365 [](start = 33, length = 6)
I think the only error in this code is the LangVer related one. #Closed
if (ReferenceEquals(initializerOpt, null)) | ||
if (initializerOpt?.HasAnyErrors != false) | ||
{ | ||
return false; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does a test cover this path? #Closed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
|
||
// Note: you will need to re-generate compiler code after adding warnings (build\scripts\generate-compiler-code.cmd) | ||
|
||
ERR_ExprCannotBeFixed = 9365, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please insert the error codes above the comment #Closed
/// fixed(int* ptr = &v){ ... } == becomes ===> | ||
/// | ||
/// pinned ref int pinnedTemp = ref v; // pinning managed ref | ||
/// int* ptr = (int*)&pinnedTemp; // unsafe cast to unmanaged ptr |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: use CDATA to avoid escaping ampersand #Resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
With the CDATA, "&" can be changed to "&" on the first line.
In reply to: 167655416 [](ancestors = 167655416)
|
||
// .GetPinnable() | ||
var getPinnableCall = getPinnableMethod.IsStatic? | ||
factory.Call(null, getPinnableMethod, callReceiver): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: different indentation than the rest of the code #Closed
} | ||
|
||
// .GetPinnable() | ||
var getPinnableCall = getPinnableMethod.IsStatic? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The meaning here is really to distinguish regular methods from extension methods, is that right? #Closed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, but we are already in lowering, so we should emit an ordinary static call.
In reply to: 167659923 [](ancestors = 167659923)
} | ||
"; | ||
|
||
var compVerifier = CompileAndVerify(text, options: TestOptions.UnsafeReleaseExe, verify: Verification.Fails, expectedOutput: @"5456"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
verify: Verification.Fails [](start = 93, length = 26)
Would this be verifiable by some updated verification rules? #Closed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The ref-returning method will be, but any use of int *
will not
In reply to: 167672486 [](ancestors = 167672486)
Error(diagnostics, ErrorCode.ERR_ManagedAddr, initializerSyntax, elementType); | ||
hasErrors = true; | ||
// See ExpressionBinder::BindPtrToArray (though most of that functionality is now in LocalRewriter). | ||
var arrayType = (ArrayTypeSymbol)initializerType; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Consider inlining, since used once #Closed
VariableDeclaratorSyntax declarator = fixedInitializer.Syntax.FirstAncestorOrSelf<VariableDeclaratorSyntax>(); | ||
Debug.Assert(declarator != null); | ||
|
||
pinnedTemp = factory.SynthesizedLocal( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
// pinned ref int pinnedTemp #Closed
getPinnableCall, | ||
isRef: true); | ||
|
||
// &pinnedTemp; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No semi-colon #Resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done with review pass (iteration 8)
SyntaxNode initializerSyntax = initializerOpt.Syntax; | ||
|
||
if (ReferenceEquals(initializerType, null)) | ||
if ((object)initializerType == null) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe is null
now? #WontFix
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
== null
is used throughout the file. I think we might want to fix that globally, but one change would just look odd.
In reply to: 167688236 [](ancestors = 167688236)
{ | ||
if (initializer.Type.SpecialType == SpecialType.System_Void) | ||
{ | ||
return null; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this method should be suffixed with "Opt" if it can return null
. #Resolved
((object)getPinnableMethod == null || !extensisbleFixedEnabled || getPinnableMethod.ContainingType.SpecialType != SpecialType.System_String)) | ||
{ | ||
elementType = this.GetSpecialType(SpecialType.System_Char, diagnostics, initializerSyntax); | ||
Debug.Assert(!elementType.IsManagedType); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't understand what this assert is supposed to protect against. If the user code is wrong, why would we assert instead of provide an error? #Resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
|
||
Error(diagnostics, ErrorCode.ERR_ExprCannotBeFixed, initializerSyntax); | ||
} | ||
finally |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This finally
seems overly complicated. Couldn't we just stick the Error
call in an else
and always call this at the end of the if/else tree? That would remove all the break
statements as well. #Resolved
Diagnostic(ErrorCode.ERR_FixedNotNeeded, "&i"), | ||
// (14,26): error CS0213: You cannot use the fixed statement to take the address of an already fixed expression | ||
Diagnostic(ErrorCode.ERR_FixedNotNeeded, "&i").WithLocation(7, 23), | ||
// (14,26): error CS9365: The given expression cannot be used in a fixed statement |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This error message seems worse (less specific) than the previous one. Why did it change? #ByDesign
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The old message was actually incorrect. "already fixed" would apply if &
was used. Here we do not use &
. It might actually work if there is an extension method of right shape., but most likely it is just a typo.
I do not think the new message is worse than the old one.
In reply to: 167710934 [](ancestors = 167710934)
"; | ||
CreateStandardCompilation(text, options: TestOptions.UnsafeReleaseDll).VerifyDiagnostics( | ||
// (20,23): error CS0254: The right hand side of a fixed statement assignment may not be a cast expression | ||
// (20,23): error CS9365: The given expression cannot be used in a fixed statement |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same here. #ByDesign
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the old message was pretty random here. It only looks like it is more appropriate. The expression that we try to fix makes no sense in many ways.
I think this is the case where user of fixed
should read documentation or check web for examples. It is not a lot to ask when unsafe features are involved.
In reply to: 167711048 [](ancestors = 167711048)
} | ||
else | ||
{ | ||
// if feature was enabled but somethng went wrong with the method, report that |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This comment seems out-of-date
Never mind #Closed
} | ||
else | ||
{ | ||
// if feature was enabled but somethng went wrong with the method, report that |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Typo: something #Resolved
} | ||
else | ||
{ | ||
// if feature was enabled but somethng went wrong with the method, report that |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we should report that the method was wrong regardless of the feature being enabled.
The errors should provide you a path (instructions) to enabling the feature and putting an appropriate method in place. #Resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I disagree on that.
The extra errors are primarily for those who develop the method, and they should have the feature enabled in the first place.
For others it would be just an annoyance - "please bump up the version to enable the feature, that you still cannot use and will only get more errors since the method that you very likely do not own or can change is broken"
I think if we have both cases - 1) feature is not enabled and 2) feature is unusable, we should not give extra diagnostics since that would not be actionable.
In reply to: 169487046 [](ancestors = 169487046)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Initially I did not consider that the diagnostics that we could not find a helper method is interesting at all. It was very noisy.
We would get those errors anywhere we try to pin something that we cannot pin (since nearly everything now can be pinned via an extension method, if such available).
Then I figured that there is one situation when you want those errors - when you are designing the actual helper, so I added some heuristics to detect that.
My criteria are - the feature is enabled and the method is "almost there, but not quite right".
Then I believe that someone is developing the helper and would want to see the extra diagnostics.
In reply to: 169494680 [](ancestors = 169494680,169487046)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK. That works
} | ||
|
||
// check for a special ref-returning method | ||
var additionalDiagnostics = DiagnosticBag.GetInstance(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What was the motivation for removing the try/catch for freeing this instance? #Resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Andy's comment on this PR. I was undecided which way is better, so if someone also thinks that try/finally makes this more complex than needed, I've removed it
In reply to: 169487211 [](ancestors = 169487211)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done with review pass (iteration 9)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
@dotnet/roslyn-compiler - PING. One more review is needed. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
Thanks!! |
Implemented "exstensible fixed statements"
The feature expands support of
fixed
to types that provide a special ref-returning method as described in https://github.com/dotnet/csharplang/blob/master/proposals/csharp-7.3/pattern-based-fixed.md(currently only arrays and strings can be used directly in
fixed
)NOTE: the name of the method is likely to change. It will be updated in subsequent changes.